/**
* @copyright Copyright (c) 2022-2023 Grab GEO IoT Team
* @file observer.c
* @author wenzhu.tu
* @version 0.1
* @date 2023-03-22
* @brief observer mode
*/

/******************************* Includes ********************************/
#include "observer.h"


/******************************* Macros ******************************/

/******************************* Typedef ******************************/


/******************************* Variables ******************************/

/******************************* Functions ******************************/

static void list_del(observer_handle_t *head, observer_handle_t *node)
{
    if(head == NULL || node == NULL)
    {
        OBSERVER_E("head or node is null");
        return;
    }

    if(head == node)
    {
        OBSERVER_E("head is not allow delete");
        return;
    }

    observer_handle_t *cur = head->next;
    while(cur != head)
    {
        if(cur == node)
        {
            observer_handle_t *last_node = cur->last;
            observer_handle_t *next_node = cur->next;
            last_node->next = next_node;
            next_node->last = last_node;
            free(cur);
            break;
        }
        cur = cur->next;
    } 

}

static void list_add(observer_handle_t *head, observer_handle_t *node)
{
    if(head == NULL || node == NULL)
    {
        OBSERVER_E("head or node is null");
        return;
    }
    
    observer_handle_t *last_node = head;
    observer_handle_t *next_node = head->next;
    node->last = last_node;
    last_node->next = node;
    next_node->last = node;
    node->next = next_node;

}

static observer_handle_t *list_find_node(observer_handle_t *head, event_callback cb)
{
    if(head == NULL || cb == NULL)
    {
        OBSERVER_E("head or cb is null");
        return NULL;
    }

    observer_handle_t *node = head->next;

    while(node)
    {
        if(node == head)
        {
            return NULL;
        }

        if(node->cb == cb)
        {
            return node;
        }

        node = node->next;
    }

    return NULL;
}


observer_handle_t *observer_init()
{
    observer_handle_t *handle = malloc(sizeof(observer_handle_t));
    if(handle == NULL)
    {
        OBSERVER_E("handle malloc fail");
        return NULL;
    }

    handle->last = handle;
    handle->next = handle;

    pthread_mutex_init(&handle->mutex, NULL);

    return handle;
}


void observer_deinit(observer_handle_t **handle)
{
    observer_handle_t *_handle = *handle;
    if(_handle == NULL)
    {
        OBSERVER_E("_handle is null");
        return;
    }

    pthread_mutex_lock(&_handle->mutex);
    observer_handle_t *temp = _handle->next;
    while(temp != _handle)
    {
        observer_handle_t *next = temp->next;
        list_del(_handle, temp);  
        temp = next;
    }
    pthread_mutex_unlock(&_handle->mutex);
    pthread_mutex_destroy(&_handle->mutex);
    _handle->last = NULL;
    _handle->next = NULL;
    free(_handle);
    _handle = NULL;
}


void observer_process(observer_handle_t *handle, int id, void *data)
{
     pthread_mutex_lock(&handle->mutex);
     observer_handle_t *temp = handle->next;
     while(temp)
     {
         if(handle == temp)
         {
            break;
         }
         
         if(temp->cb)
         {
            temp->cb(id, data);
         }

         temp = temp->next;
     }
     pthread_mutex_unlock(&handle->mutex);
}  

int  observer_add(observer_handle_t *handle, event_callback cb)
{
     if(handle == NULL)
     {
         OBSERVER_E("handle is null");
         return -1;
     }

     pthread_mutex_lock(&handle->mutex);
     observer_handle_t *node = list_find_node(handle, cb);
     if(node != NULL)
     {
         pthread_mutex_unlock(&handle->mutex);
         OBSERVER_E("that cb is exist");
         return -1;
     }
     pthread_mutex_unlock(&handle->mutex);

     node = malloc(sizeof(observer_handle_t));
     if(node == NULL)
     {
        OBSERVER_E("node malloc fail");
        return -1;
     }
     
     node->last = NULL;
     node->next = NULL;
     node->cb = cb;

     pthread_mutex_lock(&handle->mutex);
     list_add(handle, node);
     pthread_mutex_unlock(&handle->mutex);
     return 0;
}

int  observer_del(observer_handle_t *handle, event_callback cb)
{
     if(handle == NULL)
     {
         OBSERVER_E("handle is null");
         return -1;
     }

     pthread_mutex_lock(&handle->mutex);
     observer_handle_t *node = list_find_node(handle, cb);
     if(node != NULL)
     {
         list_del(handle, node);
     }
     else
     {
         OBSERVER_E("cb is not exist");
     }
     pthread_mutex_unlock(&handle->mutex);
     return 0;
}


void event_callback3(int id, void *data)
{
     OBSERVER_D("id: %d data: %s", id, (char *)data);
}

void event_callback1(int id, void *data)
{
     OBSERVER_D("id: %d data: %s", id, (char *)data);
}

void event_callback2(int id, void *data)
{
     OBSERVER_D("id: %d data: %s", id, (char *)data);
}

static void *task_test(void *usr)
{
     observer_handle_t *handle = observer_init();
     if(handle == NULL)
     {
        OBSERVER_E("handle init fail");
        return;
     }
     while(1)
     {
        observer_add(handle, event_callback3);
        observer_add(handle, event_callback3);
        observer_add(handle, event_callback1);
        observer_add(handle, event_callback2);
        observer_process(handle, 1, "dadsadas1");
        observer_del(handle, event_callback1);
        observer_process(handle, 2, "dadsadas2");
        observer_del(handle, event_callback3);
        observer_del(handle, event_callback2);
        observer_del(handle, event_callback2);
        observer_process(handle, 3, "dadsadas3");
        sleep(1);
     }
     observer_deinit(&handle);
     return NULL;
}

void observer_test()
{
    pthread_t trd;
    pthread_create(&trd, NULL, task_test, NULL);
    pthread_detach(trd);
}

/******************************* End of file ******************************/